package com.atguigu.tingshu.search.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsAggregate;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsBucket;
import co.elastic.clients.elasticsearch._types.aggregations.TopHitsAggregate;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.NestedQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.*;
import co.elastic.clients.json.JsonData;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.album.client.AlbumInfoFeignClient;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.model.album.BaseCategory3;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import io.micrometer.common.util.StringUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;


@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class SearchServiceImpl implements SearchService {

    @Autowired
    private AlbumInfoFeignClient albumInfoFeignClient;
    @Autowired
    private ElasticsearchClient elasticsearchClient;

    @Override
    @SneakyThrows
    public List<Map<String, Object>> channel(Long c1Id) {
        // 1.根据一级分类id 查询该一级分类下自定的7个三级分类（rpc  or  sendMsg）
        Result<List<BaseCategory3>> albumInfoFeignClientResult = albumInfoFeignClient.getC1Id7TopBaseCategory3List(c1Id);
        List<BaseCategory3> baseCategory3ListData = albumInfoFeignClientResult.getData();
        Assert.notNull(baseCategory3ListData, "远程调用7个置顶的3级分类对象失败");

        //2.将获取到的id转换成FieldValue(es要用)
        List<FieldValue> c3FieldValueList = baseCategory3ListData.stream().map(baseCategory3 -> {
            return FieldValue.of(baseCategory3.getId());
        }).collect(Collectors.toList());

        //3.将三级分类转换成一个map
        Map<Long, BaseCategory3> category3Map = baseCategory3ListData.stream().collect(Collectors.toMap(BaseCategory3::getId, v -> v));

        //4.查询es
        //4.1构建dsl语句
        SearchRequest searchRequest = buildChanelDsl(c3FieldValueList);
        //4.2.发送查询请求
        SearchResponse<AlbumInfoIndex> response = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);
        //4.3.解析数据
        List<Map<String, Object>> result = parseChanelResult(response, category3Map);

        //5.返回请求
        return result;
    }

    @Override
    @SneakyThrows
    public AlbumSearchResponseVo search(AlbumIndexQuery albumIndexQuery) {
        //1.构建搜索的dsl语句
        SearchRequest searchRequest = buildSearchConditionDsl(albumIndexQuery);

        //2.开始搜素
        SearchResponse<AlbumInfoIndex> search = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);

        //3.解析结果
        AlbumSearchResponseVo albumSearchResponseVo = parseSearchResult(search);
        albumSearchResponseVo.setPageSize(albumIndexQuery.getPageSize());
        albumSearchResponseVo.setPageNo(albumIndexQuery.getPageNo());
        Long total = albumSearchResponseVo.getTotal();
        Long totalPages =total%albumIndexQuery.getPageSize()==0?total/albumIndexQuery.getPageSize():total/albumIndexQuery.getPageSize()+1;
        albumSearchResponseVo.setTotalPages(totalPages);

        //4.返回结果
        return albumSearchResponseVo;
    }

    @Override
    @SneakyThrows
    public Set<String> completeSuggest(String content) {
        //1.构建提示词的dsl语句
        SearchRequest searchRequest=buildSuggestDsl(content);
        //2.开始搜索
        SearchResponse <SuggestIndex> response =elasticsearchClient.search(searchRequest,SuggestIndex.class);

        //3.解析数据(set集合未来不够10条：以搜索内容开头的标题没有10个 补标题中包含搜索关键字的 )
        Set<String> result = parseSuggestResult(response);
        if (result.size()<10){
            SearchResponse<SuggestIndex> suggestResponse = elasticsearchClient.search(sb -> sb.index("suggestinfo").query(qb -> qb.match(mqb -> mqb.field("title").query(content))), SuggestIndex.class);
            for (Hit<SuggestIndex> hit : suggestResponse.hits().hits()) {

                String title = hit.source().getTitle();
                result.add(title);
                if (result.size()>=10){
                    break;
                }
            }

        }
        //4.返回
        return result;
    }

    private Set<String> parseSuggestResult(SearchResponse<SuggestIndex> response) {
        HashSet<String> result = new HashSet<>();

        //有三对kv
        Map<String, List<Suggestion<SuggestIndex>>> suggest = response.suggest();
        for (Map.Entry<String, List<Suggestion<SuggestIndex>>> stringListEntry : suggest.entrySet()) {
            List<Suggestion<SuggestIndex>> value = stringListEntry.getValue();
            for (Suggestion<SuggestIndex> suggestIndexSuggestion : value) {
                List<CompletionSuggestOption<SuggestIndex>> options = suggestIndexSuggestion.completion().options();
                for (CompletionSuggestOption<SuggestIndex> option : options) {
                    SuggestIndex suggestIndex = option.source();
                    String title = suggestIndex.getTitle();//补全的标题
                    result.add(title);
                }
            }
        }
        return result;
    }

    //提示词构建dsl
    private SearchRequest buildSuggestDsl(String content) {
        //创建一个Suggest,builder对象
        Suggester.Builder suggestBuilder = new Suggester.Builder();
        Suggester suggester = suggestBuilder.suggesters("suggestionKeyword", fsb -> fsb.prefix(content).completion(csb -> csb.field("keyword").skipDuplicates(true)))
                //搜索的时候输入的是拼音的第一个字母
                .suggesters("suggestionKeywordSequence", fsb -> fsb.prefix(content).completion(csb -> csb.field("keywordSequence").skipDuplicates(true)))
                .suggesters("suggestionKeywordPinyin", fsb -> fsb.prefix(content).completion(csb -> csb.field("keywordPinyin").skipDuplicates(true))).build();
        SearchRequest.Builder builder = new SearchRequest.Builder();
        // 2.得到SearchRequest对象
        SearchRequest.Builder suggestinfo = builder.index("suggestinfo").suggest(suggester);
        return suggestinfo.build();

    }

    //3.解析结果
    private AlbumSearchResponseVo parseSearchResult(SearchResponse<AlbumInfoIndex> search) {
        AlbumSearchResponseVo albumSearchResponseVo = new AlbumSearchResponseVo();

        TotalHits totalHits = search.hits().total();
        long total = totalHits.value();
        albumSearchResponseVo.setTotal(total);

        ArrayList<AlbumInfoIndexVo> albumInfoIndexVos = new ArrayList<>();
        for (Hit<AlbumInfoIndex> hit : search.hits().hits()) {
            AlbumInfoIndex source = hit.source();
            AlbumInfoIndexVo albumInfoIndexVo = new AlbumInfoIndexVo();
            BeanUtils.copyProperties(source,albumInfoIndexVo);
            //高亮
            Map<String, List<String>> highlight = hit.highlight();
            List<String> albumTitle = highlight.get("albumTitle");
            if (!CollectionUtils.isEmpty(albumTitle)){
                String highlightAlbumTitle   = albumTitle.get(0);
                albumInfoIndexVo.setAlbumTitle(highlightAlbumTitle);
            }
            albumInfoIndexVos.add(albumInfoIndexVo);
        }
        albumSearchResponseVo.setList(albumInfoIndexVos);
        return albumSearchResponseVo;
    }

    //1.构建搜索的dsl语句

    /**
     * 构建搜索条件的DSL语句
     * 获取到谁家的builder就可以用谁家的api
     * 获取到searchRequestBuilder .index() .query() .from()  .size() .高亮（）
     * 获取到boolQueryBuilder .should() .must() .not() .filter()
     */
    private SearchRequest buildSearchConditionDsl(AlbumIndexQuery albumIndexQuery) {
        //1.构建dsl语句的{}
        SearchRequest.Builder searchRequestBuilder = new SearchRequest.Builder();
        // 2.构建BoolQueryBuilder对象
        BoolQuery.Builder boolQueryBuilder = new BoolQuery.Builder();
        //3.判断查询条件
        //3.判断关键字条件
        String keyword = albumIndexQuery.getKeyword();
        if (!StringUtils.isEmpty(keyword)) {
            //关键字拼接到dsl语句中
            boolQueryBuilder.should(qb -> qb.match(mqb -> mqb.field("albumTitle").query(keyword)));
            boolQueryBuilder.should(qb -> qb.match(mqb -> mqb.field("albumIntro").query(keyword)));
            boolQueryBuilder.should(qb -> qb.match(mqb -> mqb.field("announcerName").query(keyword)));
        }
        //3.2分类条件
        Long category1Id = albumIndexQuery.getCategory1Id();
        if (null != category1Id) {
            boolQueryBuilder.must(qb -> qb.term(tqb -> tqb.field("category1Id").value(category1Id)));
        }
        Long category2Id = albumIndexQuery.getCategory2Id();
        if (null != category2Id) {
            boolQueryBuilder.must(qb -> qb.term(tqb -> tqb.field("category2Id").value(category2Id)));
        }
        Long category3Id = albumIndexQuery.getCategory3Id();
        if (null != category3Id) {
            boolQueryBuilder.must(qb -> qb.term(tqb -> tqb.field("category3Id").value(category3Id)));
        }

        //3.3判断标签信息 ["2:3", "3:6"]  属性id:属性值id
        List<String> attributeList = albumIndexQuery.getAttributeList();
        if (!CollectionUtils.isEmpty(attributeList)) {
            for (String attrIdAndValueId : attributeList) {
                String[] split = attrIdAndValueId.split(":");
                String attrId = split[0];
                String attrValueId = split[1];
                // 构建NestedBuilder对象
                NestedQuery.Builder nestedBuilder = new NestedQuery.Builder();
                //用NestedBuilder对象构建nested类型的dsl
                nestedBuilder.path("attributeValueIndexList")
                        .query(qb -> qb.bool(bqb -> bqb
                                .must(mqb -> mqb
                                        .term(tqb -> tqb.field("attributeValueIndexList.attributeId").value(attrId)))
                                .must(mqb -> mqb
                                        .term(tqb -> tqb.field("attributeValueIndexList.valueId").value(attrValueId)))));
                // 得到NestedQuery对象
                NestedQuery nestedQuery = nestedBuilder.build();
                boolQueryBuilder.must(qb -> qb.nested(nestedQuery));
            }
        }

        BoolQuery boolQuery = boolQueryBuilder.build();
        Query query = boolQuery._toQuery();//将bool放到query对象中
        searchRequestBuilder.index("albuminfo").query(query);//将query放到{}中

        //3.4构建分页
        Integer pageNo = albumIndexQuery.getPageNo();
        Integer pageSize = albumIndexQuery.getPageSize();
        searchRequestBuilder.from((pageNo - 1) * pageSize).size(pageSize);

        // 3.5 构建排序 (1:asc[desc]) (2:asc[desc]) (3:asc[desc])
        String order = albumIndexQuery.getOrder();
        if (!StringUtils.isEmpty(order)) {
            String[] split = order.split(":");
            String dimension = split[0]; // 排序维度
            String rule = split[1]; // 排序规则
            SortOrder realRule = rule.equals("asc") ? SortOrder.Asc : SortOrder.Desc;
            switch (dimension) {
                case "1":
                    //热度值
                    searchRequestBuilder.sort(sob -> sob.field(fsb -> fsb.field("hotScore").order(realRule)));
                case "2":
                    //播放量
                    searchRequestBuilder.sort(sob -> sob.field(fsb -> fsb.field("playStatNum").order(realRule)));
                case "3":
                    //发布时间
                    searchRequestBuilder.sort(sob -> sob.field(fsb -> fsb.field("createTime").order(realRule)));
            }
        } else {
            //如果排序为空按照热度值排序
            searchRequestBuilder.sort(sob -> sob.field(fsb -> fsb.field("hotScore").order(SortOrder.Desc)));
        }

        //设置高亮
        searchRequestBuilder.highlight(hb->hb.fields("albumTitle",hfb->hfb.preTags("<font style='color:red'>").postTags("</font>")));

        //4.构建SearchRequest对象
        SearchRequest SearchRequest = searchRequestBuilder.build();
        System.out.println("build = " + SearchRequest.toString());
        return SearchRequest;
}

/*
 * @author brendan
 * @description TODO 解析数据
 * @date 2025/1/20 21:52
 **/

private List<Map<String, Object>> parseChanelResult(SearchResponse<AlbumInfoIndex> response, Map<Long, BaseCategory3> category3Map) {

    ArrayList<Map<String, Object>> result = new ArrayList<>();
    // 1.根据response对象获取aggregations对象（父aggregations对象）
    Aggregate category3IdAggs = response.aggregations().get("category3IdAggs");
    // 2.将Aggregate转成LongAggregate精准的聚合类型
    LongTermsAggregate lterms = category3IdAggs.lterms();
    // 3.从lterms获取桶
    List<LongTermsBucket> buckets = lterms.buckets().array();
    for (LongTermsBucket bucket : buckets) {
        // map是每一个三级分类对象（为这个Map存两对key,value）
        long c3Id = bucket.key();
        HashMap<String, Object> map = new HashMap<>();
        map.put("baseCategory3", category3Map.get(c3Id));  //baseCategory3这个key的value是该三级分类对象
        Aggregate albumSocreTopAgg = bucket.aggregations().get("albumScoreTopAgg");
        TopHitsAggregate topHitsAggregate = albumSocreTopAgg.topHits();
        List<Hit<JsonData>> hits = topHitsAggregate.hits().hits();
        ArrayList<AlbumInfoIndex> albumInfoIndices = new ArrayList<>();
        for (Hit<JsonData> hit : hits) {
            // hit是每一个热度值高的专辑对象
            JsonData source = hit.source();
            AlbumInfoIndex albumInfoIndex = JSONObject.parseObject(source.toString(), AlbumInfoIndex.class);
            albumInfoIndices.add(albumInfoIndex);
        }
        map.put("list", albumInfoIndices);  //list这个key是这个三级分类下的6个热度值高的专辑集合
        result.add(map);
    }

    // 4.返回结果
    return result;

}

/*
 * @author brendan
 * @description TODO 构建dls
 * @date 2025/1/20 21:52
 **/
private SearchRequest buildChanelDsl(List<FieldValue> c3FieldValueList) {
    //1.构建SearchRequest对象
    SearchRequest searchRequest = SearchRequest.of(b -> b
            .index("albuminfo")
            .query(qb -> qb
                    .terms(tqb -> tqb
                            .field("category3Id")
                            .terms(tqfb -> tqfb
                                    .value(c3FieldValueList))))
            .aggregations("category3IdAggs", ab -> ab
                    .terms(tab -> tab.field("category3Id").size(c3FieldValueList.size()))
                    .aggregations("albumScoreTopAgg", sab -> sab
                            .topHits(thab -> thab
                                    .sort(sob -> sob
                                            .field(fsb -> fsb
                                                    .field("hotScore")
                                                    .order(SortOrder.Desc)))))));
    System.out.println("频道页的dsl:" + searchRequest.toString());
    return searchRequest;
}
}
